File: LanguageService\AbstractLanguageService`2.VsCodeWindowManager.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServices.DocumentOutline;
using Microsoft.VisualStudio.LanguageServices.Implementation.NavigationBar;
using Microsoft.VisualStudio.LanguageServices.Utilities;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Outlining;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
 
internal abstract partial class AbstractLanguageService<TPackage, TLanguageService>
{
    internal class VsCodeWindowManager : IVsCodeWindowManager, IVsCodeWindowEvents, IVsDocOutlineProvider
    {
        private readonly TLanguageService _languageService;
        private readonly IVsCodeWindow _codeWindow;
        private readonly ComEventSink _sink;
        private readonly IGlobalOptionService _globalOptions;
 
        private IDisposable? _navigationBarController;
        private IVsDropdownBarClient? _dropdownBarClient;
        private ElementHost? _documentOutlineViewHost;
        private DocumentOutlineView? _documentOutlineView;
 
        public VsCodeWindowManager(TLanguageService languageService, IVsCodeWindow codeWindow)
        {
            _languageService = languageService;
            _codeWindow = codeWindow;
 
            _globalOptions = languageService.Package.ComponentModel.GetService<IGlobalOptionService>();
 
            _sink = ComEventSink.Advise<IVsCodeWindowEvents>(codeWindow, this);
            _globalOptions.AddOptionChangedHandler(this, GlobalOptionChanged);
        }
 
        private void SetupView(IVsTextView view)
            => _languageService.SetupNewTextView(view);
 
        private void GlobalOptionChanged(object sender, object target, OptionChangedEventArgs e)
        {
            if (e.ChangedOptions.Any(item => item.key.Language == _languageService.RoslynLanguageName && item.key.Option.Equals(NavigationBarViewOptionsStorage.ShowNavigationBar)))
            {
                AddOrRemoveDropdown();
            }
        }
 
        private void AddOrRemoveDropdown()
        {
            if (_codeWindow is not IVsDropdownBarManager dropdownManager)
            {
                return;
            }
 
            if (ErrorHandler.Failed(_codeWindow.GetBuffer(out var buffer)) || buffer == null)
            {
                return;
            }
 
            var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(buffer);
            var document = textBuffer?.AsTextContainer()?.GetRelatedDocuments().FirstOrDefault();
            // TODO - Remove the TS check once they move the liveshare navbar to LSP.  Then we can also switch to LSP
            // for the local navbar implementation.
            // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1163360
            if (textBuffer?.IsInLspEditorContext() == true && document!.Project!.Language != InternalLanguageNames.TypeScript)
            {
                // Remove the existing dropdown bar if it is ours.
                if (IsOurDropdownBar(dropdownManager, out var _))
                {
                    RemoveDropdownBar(dropdownManager);
                }
 
                return;
            }
 
            var enabled = _globalOptions.GetOption(NavigationBarViewOptionsStorage.ShowNavigationBar, _languageService.RoslynLanguageName);
            if (enabled)
            {
                if (IsOurDropdownBar(dropdownManager, out var existingDropdownBar))
                {
                    // The dropdown bar is already one of ours, do nothing.
                    return;
                }
 
                if (existingDropdownBar != null)
                {
                    // Not ours, so remove the old one so that we can add ours.
                    RemoveDropdownBar(dropdownManager);
                }
                else
                {
                    Contract.ThrowIfFalse(_navigationBarController == null, "We shouldn't have a controller manager if there isn't a dropdown");
                    Contract.ThrowIfFalse(_dropdownBarClient == null, "We shouldn't have a dropdown client if there isn't a dropdown");
                }
 
                AddDropdownBar(dropdownManager);
            }
            else
            {
                RemoveDropdownBar(dropdownManager);
            }
 
            bool IsOurDropdownBar(IVsDropdownBarManager dropdownBarManager, out IVsDropdownBar? existingDropdownBar)
            {
                existingDropdownBar = GetDropdownBar(dropdownBarManager);
                if (existingDropdownBar != null)
                {
                    if (_dropdownBarClient != null &&
                        _dropdownBarClient == GetDropdownBarClient(existingDropdownBar))
                    {
                        return true;
                    }
                }
 
                return false;
            }
        }
 
        private static IVsDropdownBar GetDropdownBar(IVsDropdownBarManager dropdownManager)
        {
            ErrorHandler.ThrowOnFailure(dropdownManager.GetDropdownBar(out var existingDropdownBar));
            return existingDropdownBar;
        }
 
        private static IVsDropdownBarClient GetDropdownBarClient(IVsDropdownBar dropdownBar)
        {
            ErrorHandler.ThrowOnFailure(dropdownBar.GetClient(out var dropdownBarClient));
            return dropdownBarClient;
        }
 
        private void AddDropdownBar(IVsDropdownBarManager dropdownManager)
        {
            if (ErrorHandler.Failed(_codeWindow.GetBuffer(out var buffer)))
            {
                return;
            }
 
            var navigationBarClient = new NavigationBarClient(dropdownManager, _codeWindow, _languageService.SystemServiceProvider, _languageService.Workspace);
            var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(buffer);
            var controllerFactoryService = _languageService.Package.ComponentModel.GetService<INavigationBarControllerFactoryService>();
            var newController = controllerFactoryService.CreateController(navigationBarClient, textBuffer);
            var hr = dropdownManager.AddDropdownBar(cCombos: 3, pClient: navigationBarClient);
 
            if (ErrorHandler.Failed(hr))
            {
                newController.Dispose();
                ErrorHandler.ThrowOnFailure(hr);
            }
 
            _navigationBarController = newController;
            _dropdownBarClient = navigationBarClient;
            return;
        }
 
        private void RemoveDropdownBar(IVsDropdownBarManager dropdownManager)
        {
            if (ErrorHandler.Succeeded(dropdownManager.RemoveDropdownBar()))
            {
                if (_navigationBarController != null)
                {
                    _navigationBarController.Dispose();
                    _navigationBarController = null;
                }
 
                _dropdownBarClient = null;
            }
        }
 
        public int AddAdornments()
        {
            int hr;
            if (ErrorHandler.Failed(hr = _codeWindow.GetPrimaryView(out var primaryView)))
            {
                Debug.Fail("GetPrimaryView failed in IVsCodeWindowManager.AddAdornments");
                return hr;
            }
 
            SetupView(primaryView);
            if (ErrorHandler.Succeeded(_codeWindow.GetSecondaryView(out var secondaryView)))
            {
                SetupView(secondaryView);
            }
 
            AddOrRemoveDropdown();
 
            return VSConstants.S_OK;
        }
 
        public int OnCloseView(IVsTextView view)
        {
            return VSConstants.S_OK;
        }
 
        public int OnNewView(IVsTextView view)
        {
            SetupView(view);
 
            return VSConstants.S_OK;
        }
 
        public int RemoveAdornments()
        {
            _sink.Unadvise();
            _globalOptions.RemoveOptionChangedHandler(this, GlobalOptionChanged);
 
            if (_codeWindow is IVsDropdownBarManager dropdownManager)
            {
                RemoveDropdownBar(dropdownManager);
            }
 
            return VSConstants.S_OK;
        }
 
        // GetOutline is called every time a new code window is created. Whenever we switch to a different window, it is guaranteed
        // that ReleaseOutline will be called on the old window before GetOutline is called for the new window. 
        int IVsDocOutlineProvider.GetOutline(out IntPtr phwnd, out IOleCommandTarget? pCmdTarget)
        {
            pCmdTarget = null;
            GetOutline(out phwnd);
            return VSConstants.S_OK;
        }
 
        private void GetOutline(out IntPtr phwnd)
        {
            phwnd = default;
 
            var enabled = _globalOptions.GetOption(DocumentOutlineOptionsStorage.EnableDocumentOutline)
                ?? !_globalOptions.GetOption(DocumentOutlineOptionsStorage.DisableDocumentOutlineFeatureFlag);
            if (!enabled)
                return;
 
            var threadingContext = _languageService.Package.ComponentModel.GetService<IThreadingContext>();
            threadingContext.ThrowIfNotOnUIThread();
 
            var uiShell = (IVsUIShell4)_languageService.SystemServiceProvider.GetService(typeof(SVsUIShell));
            var windowSearchHostFactory = (IVsWindowSearchHostFactory)_languageService.SystemServiceProvider.GetService(typeof(SVsWindowSearchHostFactory));
            var languageServiceBroker = _languageService.Package.ComponentModel.GetService<ILanguageServiceBroker2>();
            var asyncListenerProvider = _languageService.Package.ComponentModel.GetService<IAsynchronousOperationListenerProvider>();
            var asyncListener = asyncListenerProvider.GetListener(FeatureAttribute.DocumentOutline);
            var editorAdaptersFactoryService = _languageService.Package.ComponentModel.GetService<IVsEditorAdaptersFactoryService>();
            var outliningManagerService = _languageService.Package.ComponentModel.GetService<IOutliningManagerService>();
 
            // Assert that the previous Document Outline Control and host have been freed. 
            Contract.ThrowIfFalse(_documentOutlineView is null);
            Contract.ThrowIfFalse(_documentOutlineViewHost is null);
 
            var viewTracker = new VsCodeWindowViewTracker(_codeWindow, threadingContext, editorAdaptersFactoryService);
            _documentOutlineView = new DocumentOutlineView(
                uiShell, windowSearchHostFactory, threadingContext, _globalOptions, outliningManagerService, viewTracker,
                new DocumentOutlineViewModel(threadingContext, viewTracker, languageServiceBroker, asyncListener));
 
            _documentOutlineViewHost = new ElementHost
            {
                Dock = DockStyle.Fill,
                Child = _documentOutlineView
            };
 
            phwnd = _documentOutlineViewHost.Handle;
 
            Logger.Log(FunctionId.DocumentOutline_WindowOpen, logLevel: LogLevel.Information);
        }
 
        int IVsDocOutlineProvider.ReleaseOutline(IntPtr hwnd, IOleCommandTarget pCmdTarget)
        {
            var threadingContext = _languageService.Package.ComponentModel.GetService<IThreadingContext>();
            threadingContext.ThrowIfNotOnUIThread();
 
            if (_documentOutlineView is not null &&
                _documentOutlineViewHost is not null)
            {
                _documentOutlineViewHost.SuspendLayout();
                _documentOutlineView.Dispose();
                _documentOutlineView = null;
                _documentOutlineViewHost.Child = null;
                _documentOutlineViewHost.Parent = null;
                _documentOutlineViewHost.Dispose();
                _documentOutlineViewHost = null;
            }
 
            return VSConstants.S_OK;
        }
 
        int IVsDocOutlineProvider.GetOutlineCaption(VSOUTLINECAPTION nCaptionType, out string pbstrCaption)
        {
            pbstrCaption = ServicesVSResources.Document_Outline;
            return VSConstants.S_OK;
        }
 
        int IVsDocOutlineProvider.OnOutlineStateChange(uint dwMask, uint dwState)
        {
            return VSConstants.S_OK;
        }
    }
}